package com.jprocms.module.cms.service.job;

import cn.hutool.core.collection.CollUtil;
import com.jpro.framework.quartz.client.constant.ExecutorBlockStrategyEnum;
import com.jpro.framework.quartz.client.constant.ExecutorRouteStrategyEnum;
import com.jpro.framework.quartz.client.util.ScheduleUtil;
import com.jpro.framework.quartz.client.vo.JobInfo;
import com.jpro.framework.quartz.client.vo.XxlJobInfo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jprocms.module.cms.convert.job.JobConvert;
import com.jprocms.module.cms.dal.dataobject.job.JobDO;
import com.jprocms.module.cms.dal.mysql.job.JobMapper;
import com.jprocms.module.cms.dto.JobDto;
import com.jprocms.module.cms.enums.EnumJobStatus;
import com.jprocms.module.cms.enums.EnumXxlJobTriggerStatus;
import com.jprocms.module.cms.framework.job.util.XxlJobUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;


/**
 * 定时任务 Service 实现类
 *
 * @author jprocms
 */
@Slf4j
@Service
@Validated
public class JobServiceImpl implements JobService {

    @Resource
    private JobMapper jobMapper;


    @Override
    public void addOrUpdateJob(JobDto jobDto) {
        JobDO JobDO = selectOneByJobTypeAndDataId(jobDto.getJobType(), jobDto.getDataId());
        if (JobDO == null) {
            createJob(jobDto);
        } else {
            updateJob(jobDto, JobDO);
        }
    }

    @Override
    public void deleteByJobTypeAndDataId(Integer jobType, Long dataId) {
        JobDO jobDO = selectOneByJobTypeAndDataId(jobType, dataId);
        if (jobDO != null) {
            deleteXxlJob(jobDO);
        }
    }

    @Override
    public void deleteByJobType(Integer jobType) {
        List<JobDO> jobDOS = selectByJobType(jobType);
        if (CollUtil.isNotEmpty(jobDOS)) {
            jobDOS.forEach(((JobDO jobDO) -> {
                deleteXxlJob(jobDO);
            }));
        }
    }

    @Override
    public void restoreByJobTypeAndDataId(Integer jobType, Long dataId) {
        JobDO jobDO = selectOneByJobTypeAndDataId(jobType, dataId);
        if (jobDO != null) {
            restoreXxlJob(jobDO);
        }
    }

    private void restoreXxlJob(JobDO job) {
        if (EnumJobStatus.INIT.getCode().equals(job.getStatus())) {
            boolean isSuccess = false;
            Integer xxlJobId = null;
            try {
                xxlJobId = addJobToXxlJobServer(job, true);
                isSuccess = true;
            } catch (Exception e) {
                log.error("同步定时任务id:{}到xxljobserver失败", job.getId(), e);
            }
            if (isSuccess) {
                job.setXxlJobId(xxlJobId);
                job.setStatus(EnumJobStatus.RUNABLE.getCode());
            }
            jobMapper.updateById(job);
        } else if (EnumJobStatus.STOP.getCode().equals(job.getStatus())) {
            boolean isSuccess = false;
            try {
                startXxlJob(job.getXxlJobId());
                isSuccess = true;
            } catch (Exception e) {
                log.error("启动定时任务id:{}到xxljobserver失败", job.getId(), e);
            }
            if (isSuccess) {
                job.setStatus(EnumJobStatus.RUNABLE.getCode());
            }
            jobMapper.updateById(job);
        }
    }

    @Override
    public void updateJobStatus(Long jobId, Integer status) {
        if (jobId != null && status != null) {
            JobDO job = new JobDO();
            job.setId(jobId);
            job.setStatus(status);
            jobMapper.updateById(job);
        }
    }

    public List<JobDO> selectByJobTypeAndDataId(Integer jobType, Long dataId) {
        if (jobType == null || dataId == null) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<JobDO> queryWrapper = Wrappers.lambdaQuery(JobDO.class);
        queryWrapper.eq(JobDO::getJobType, jobType);
        queryWrapper.eq(JobDO::getDataId, dataId);
        return jobMapper.selectList(queryWrapper);
    }

    public JobDO selectOneByJobTypeAndDataId(Integer jobType, Long dataId) {
        List<JobDO> jobDOS = selectByJobTypeAndDataId(jobType, dataId);
        if (!CollUtil.isEmpty(jobDOS)) {
            return jobDOS.get(0);
        }
        return null;
    }

    public List<JobDO> selectByJobType(Integer jobType) {
        if (jobType == null) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<JobDO> queryWrapper = Wrappers.lambdaQuery(JobDO.class);
        queryWrapper.eq(JobDO::getJobType, jobType);
        return jobMapper.selectList(queryWrapper);
    }

    private void startXxlJob(Integer externId) {
        ScheduleUtil.startJob(externId);
    }

    private void deleteXxlJob(JobDO jobDO) {
        boolean isSuccess = false;
        try {
            long cur = System.currentTimeMillis();
            if (jobDO.getXxlJobId() != null) {
                ScheduleUtil.deleteJob(jobDO.getXxlJobId().longValue());
            }
            log.info("deleteJob extends :{} ms", System.currentTimeMillis() - cur);
            isSuccess = true;
        } catch (Exception e) {
            log.error("删除job:{} 到xxljobserver失败", jobDO.getId(), e);
        }
        if (isSuccess) {
            jobMapper.deleteById(jobDO.getId());
        }
    }

    private void createJob(JobDto job) {
        JobDO JobDO = JobConvert.INSTANCE.convert(job);
        if (JobDO != null) {
            JobDO.setStatus(EnumJobStatus.INIT.getCode());
            jobMapper.insert(JobDO);
        }
        if (!EnumJobStatus.INIT.getCode().equals(job.getStatus())) {
            JobDO.setStatus(job.getStatus());
            syncJobToXxlJobServer(JobDO);
        }
    }

    private void updateJob(JobDto job, JobDO JobDO) {
        JobDO newJob = JobConvert.INSTANCE.convert(job);
        newJob.setId(JobDO.getId());
        jobMapper.updateById(newJob);
        if (needUpdateXxlServerJob(job, JobDO)) {
            Integer xxlJobId = JobDO.getXxlJobId();
            if (xxlJobId == null) {
                newJob.setStatus(job.getStatus());
                syncJobToXxlJobServer(newJob);
                return;
            }
            newJob.setXxlJobId(xxlJobId);
            updateXxlJob(newJob);
            if (job.getStatus() != null) {
                if (EnumJobStatus.RUNABLE.getCode().equals(job.getStatus())) {
                    ScheduleUtil.startJob(xxlJobId);
                } else {
                    ScheduleUtil.stopJob(xxlJobId);
                }
                JobDO statusJob = new JobDO();
                statusJob.setStatus(job.getStatus());
                statusJob.setId(JobDO.getId());
                jobMapper.updateById(statusJob);
            }
        }
    }

    private void syncJobToXxlJobServer(JobDO job) {

        boolean isSuccess = false;
        Integer externId = null;
        boolean isRunJob = EnumJobStatus.RUNABLE.getCode().equals(job.getStatus()) ? true : false;
        try {
            externId = addJobToXxlJobServer(job, isRunJob);
            isSuccess = true;
        } catch (Exception e) {
            log.error("同步定时任务id:{}到xxljobserver失败", job.getId(), e);
        }
        if (isSuccess) {
            job.setXxlJobId(externId);
            jobMapper.updateById(job);
        }
    }

    /**
     * 请求xxl 创建 定时任务
     *
     * @param job      job信息
     * @param isRunJob
     * @return
     */
    private Integer addJobToXxlJobServer(JobDO job, boolean isRunJob) {
        JobInfo jobInfo = new JobInfo();
        EnumXxlJobTriggerStatus executorRouteStrategyEnum = isRunJob ?
                EnumXxlJobTriggerStatus.RUNNING :
                EnumXxlJobTriggerStatus.STOP;
        jobInfo.setJobCron(job.getCron());
        jobInfo.setExecutorHandler(job.getJobHandler());
        jobInfo.setExecutorBlockStrategyEnum(ExecutorBlockStrategyEnum.DISCARD_LATER.name());
        jobInfo.setExecutorRouteStrategyEnum(ExecutorRouteStrategyEnum.LEAST_FREQUENTLY_USED.name());
        jobInfo.setTriggerStatus(executorRouteStrategyEnum.getCode());
        jobInfo.setExecutorParam(XxlJobUtil.parseToXxlParam(job.getId(), job.getParams()));
        jobInfo.setJobDesc(job.getJobName());
        return ScheduleUtil.addJob(jobInfo);
    }

    private boolean needUpdateXxlServerJob(JobDto job, JobDO jobDO) {
        if (jobDO.getXxlJobId() == null) {
            return true;
        }
        XxlJobInfo jobInfo = ScheduleUtil.getById(jobDO.getXxlJobId());
        if (jobInfo == null) {
            return true;
        }
        if (job.getCron() != null && !job.getCron().equals(jobDO.getCron())) {
            return true;
        }
        if (job.getJobHandler() != null && !job.getJobHandler().equals(jobDO.getJobHandler())) {
            return true;
        }
        if (job.getParams() != null && !job.getParams().equals(jobDO.getParams())) {
            return true;
        }
        if (job.getStatus() != null && !job.getStatus().equals(jobDO.getStatus())) {
            return true;
        }
        return false;
    }

    private void updateXxlJob(JobDO job) {
        JobInfo jobInfo = new JobInfo();
        jobInfo.setId(job.getXxlJobId());
        jobInfo.setJobCron(job.getCron());
        jobInfo.setExecutorHandler(job.getJobHandler());
        jobInfo.setJobDesc(job.getJobName());
        jobInfo.setExecutorParam(XxlJobUtil.parseToXxlParam(job.getId(), job.getParams()));
        try {
            ScheduleUtil.updateJob(jobInfo);
        } catch (Exception e) {
            log.error("更新job:{} 到xxljobserver失败", job.getId(), e);
        }
    }


}
